﻿using System;
using System.IO;
using log4net;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;

namespace JiraRest
{
    public static class JiraJsonSerializer
    {
        private static readonly ILog s_Log = LogManager.GetLogger("Jira.JiraJsonSerializer");
        private static readonly JsonSerializer s_JsonSerializer;

        static JiraJsonSerializer()
        {
            s_JsonSerializer = new JsonSerializer();
            s_JsonSerializer.ReferenceLoopHandling = ReferenceLoopHandling.Error;
            s_JsonSerializer.MissingMemberHandling = MissingMemberHandling.Ignore;
            s_JsonSerializer.NullValueHandling = NullValueHandling.Ignore;
            s_JsonSerializer.DefaultValueHandling = DefaultValueHandling.Ignore;
            s_JsonSerializer.ObjectCreationHandling = ObjectCreationHandling.Auto;
            s_JsonSerializer.TypeNameHandling = TypeNameHandling.None;
            s_JsonSerializer.ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor;
            s_JsonSerializer.Converters.Add(new IsoDateTimeConverter());
            s_JsonSerializer.Converters.Add(new ProductRefConverter());
        }

        public static string Serialize<TPropertyBag>(TPropertyBag propertyBag)
            where TPropertyBag : class, new()
        {
            using (StringWriter stringWriter = new StringWriter())
            {
                using (JsonTextWriter jsonTextWriter = new JsonTextWriter(stringWriter))
                {
                    jsonTextWriter.Formatting = Formatting.Indented;
                    jsonTextWriter.Indentation = 2;
                    jsonTextWriter.IndentChar = ' ';
                    jsonTextWriter.QuoteName = true;
                    jsonTextWriter.QuoteChar = '"';
                    s_JsonSerializer.Serialize(jsonTextWriter, propertyBag);
                }
                string serializedPropertyBag = stringWriter.ToString();
                return serializedPropertyBag;
            }
        }

        internal static TPropertyBag Populate<TPropertyBag>(string serializedPropertyBag)
            where TPropertyBag : class, new()
        {
            using (StringReader stringReader = new StringReader(serializedPropertyBag))
            {
                using (JsonTextReader jsonTextReader = new JsonTextReader(stringReader))
                {
                    TPropertyBag ret = new TPropertyBag();
                    s_JsonSerializer.Populate(jsonTextReader, ret);
                    return ret;
                }
            }
        }

        public static bool TryPopulate<TPropertyBag>(string serializedPropertyBag, out TPropertyBag propertyBag)
            where TPropertyBag : class, new()
        {
            try
            {
                char[] newline = Environment.NewLine.ToCharArray();
                serializedPropertyBag = " " + serializedPropertyBag; //HACK to make the for loop below work

                for (int index = 0; index != -1; index = serializedPropertyBag.IndexOfAny(newline, index + 1))
                {
                    try
                    {
                        propertyBag = Populate<TPropertyBag>(serializedPropertyBag.Substring(index + 1));
                        return true;
                    }
                    catch (JsonReaderException)
                    {
                        //e.g. Unexpected character encountered while parsing value: W. Line 1, position 1.
                    }
                    catch (JsonSerializationException)
                    {
                        //e.g. Unexpected initial token 'Float' when populating object. Expected JSON object or array.
                    }
                    catch (FormatException)
                    {
                        //e.g. Input string was not in a correct format.
                    }
                    catch (ArgumentOutOfRangeException)
                    {
                        //e.g. Index was out of range. Must be non-negative and less than the size of the collection. Parameter name: index
                    }
                }
            }
            catch (Exception e)
            {
                s_Log.Warn(String.Format("Exception finding the JSON in {0}", serializedPropertyBag), e);

                propertyBag = null;
                return false;
            }

            propertyBag = null;
            return false;
        }
    }
}
